本篇同步發文在個人Blog: 一袋.NET要扛幾樓?打造容器化的ASP.NET Core網站!系列文章 - (14) 建立會員系統 - 3
在專案AuthApi的根目錄新增類別Config.cs,這裡包含所有IdentityServer4要建立的Client、Scope、Resource。
Client定義應用程式的授權流程以及OIDC的網址,本專案對WebMvc採用混和(Hybrid)。Scope代表資源能存取的範圍、Resource代表有哪些資源。
using IdentityServer4;
using IdentityServer4.Models;
using Microsoft.Extensions.Configuration;
using System.Collections.Generic;
namespace TokenServiceApi
{
public class Config
{
public static Dictionary<string, string> GetUrls(IConfiguration configuration)
{
Dictionary<string, string> urls = new Dictionary<string, string>();
urls.Add("Mvc", configuration.GetValue<string>("MvcClient"));
return urls;
}
public static IEnumerable<ApiScope> GetApiScopes()
{
return new List<ApiScope>
{
new ApiScope("basket", "basket api"),
new ApiScope("order", "order api"),
new ApiScope("report", "report api")
};
}
public static IEnumerable<ApiResource> GetApiResources()
{
return new List<ApiResource>
{
new ApiResource("basket", "Shopping Cart Api")
{
Scopes = new List<string>
{
"basket"
}
},
new ApiResource("order", "Ordering Api")
{
Scopes = new List<string>
{
"order"
}
},
new ApiResource("report", "Report Api"){
Scopes = new List<string>
{
"report"
}
}
};
}
public static IEnumerable<IdentityResource> GetIdentityResources()
{
return new List<IdentityResource>
{
new IdentityResources.OpenId(),
new IdentityResources.Profile()
};
}
public static IEnumerable<Client> GetClients(Dictionary<string, string> clientUrls)
{
return new List<Client>
{
new Client
{
ClientId = "mvc",
ClientSecrets = new []{new Secret("secret".Sha256())},
AllowedGrantTypes = GrantTypes.Hybrid,
RedirectUris = {$"{clientUrls["Mvc"]}/signin-oidc"},
PostLogoutRedirectUris = {$"{clientUrls["Mvc"]}/signout-callback-oidc"},
AllowAccessTokensViaBrowser = false,
AllowOfflineAccess = true,
RequireConsent = false,
RequirePkce = false,
AlwaysIncludeUserClaimsInIdToken =true,
AllowedScopes = new List<string>
{
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
IdentityServerConstants.StandardScopes.OfflineAccess,
"order",
"basket",
"report"
}
}
};
}
}
}
在Models資料夾新增ApplicationUser.cs,並繼承IdentityUser,這個ApplicationUser可以自己加想要的屬性,在這我們都用空的。
在Data資料夾會有ApplicationDbContext,將它改成繼承IdentityDbContext
在Data資料夾新增IdentityDbInit.cs,注入ApplicationDbContext和UserManager,每次程式啟動時會檢查Migrations,並確認是否有測試的帳號,沒有的話則新增它。
using AuthApi.Models;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using System.Linq;
using System.Threading.Tasks;
namespace AuthApi.Data
{
public class IdentityDbInit
{
public static async Task Initialize(
ApplicationDbContext context,
UserManager<ApplicationUser> userManager)
{
context.Database.Migrate();
if (context.Users.Any(r => r.UserName == "test@test.com"))
{
return;
}
string user = "test@test.com";
string password = "P@ssword1";
await userManager.CreateAsync(new ApplicationUser { UserName = user, EmailConfirmed = true }, password);
}
}
}
程式啟動都會呼叫IdentityDbInit作資料庫的Migrations和測試帳號的新增與否
public static void Main(string[] args)
{
var host = CreateHostBuilder(args).Build();
using (var scope = host.Services.CreateScope())
{
var services = scope.ServiceProvider;
try
{
var context = services.GetRequiredService<ApplicationDbContext>();
var userManager = services.GetRequiredService<UserManager<ApplicationUser>>();
IdentityDbInit.Initialize(context, userManager).Wait();
}
catch (Exception ex)
{
var logger = services.GetRequiredService<ILogger<Program>>();
logger.LogError(ex, "An error occurred while seeding the Authorization Server database.");
}
}
host.Run();
}
註冊IdentityServer4和資料庫整合的相關服務,並在Pipeline新增IdentityServer4的middleware
using AuthApi.Data;
using AuthApi.Models;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using TokenServiceApi;
namespace AuthApi
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
string server = Configuration["DatabaseServer"];
string database = Configuration["DatabaseName"];
string user = Configuration["DatabaseUser"];
string password = Configuration["DatabasePassword"];
string connectionString = string.Format("Server={0};Database={1};User={2};Password={3};", server, database, user, password);
services.AddDbContext<ApplicationDbContext>(options => options.UseSqlServer(connectionString));
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
services.AddControllersWithViews();
services.AddRazorPages();
var builder = services.AddIdentityServer(options =>
{
options.Events.RaiseErrorEvents = true;
options.Events.RaiseInformationEvents = true;
options.Events.RaiseFailureEvents = true;
options.Events.RaiseSuccessEvents = true;
options.EmitStaticAudienceClaim = true;
})
.AddDeveloperSigningCredential()
.AddInMemoryPersistedGrants()
.AddInMemoryApiScopes(Config.GetApiScopes())
.AddInMemoryIdentityResources(Config.GetIdentityResources())
.AddInMemoryApiResources(Config.GetApiResources())
.AddInMemoryClients(Config.GetClients(Config.GetUrls(Configuration)))
.AddAspNetIdentity<ApplicationUser>();
services.AddControllersWithViews();
services.AddAuthentication();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseDatabaseErrorPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseIdentityServer();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
endpoints.MapRazorPages();
});
}
}
}
新增資料庫的連線資訊,和CatalogApi專案的方式一樣,只是資料庫名稱是AuthDb:
"DatabaseServer": "localhost,1445",
"DatabaseName": "AuthDb",
"DatabaseUser": "sa",
"DatabasePassword": "JustTest!",
專案在初始化後已經有預設的Migrations類別,在根目錄使用cmd執行遷移:
dotnet ef database update
===
使用VS執行AuthApi,在Account/Login做登入,成功的話在diagnostics會顯示JWT相關的值,如圖1與圖2
圖1
圖2